package com.le.leauth.service.Impl;


import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.secure.BCrypt;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.le.leauth.config.propertis.GiteeConfigPropertis;
import com.le.leauth.config.propertis.GithubConfigPropertis;
import com.le.leauth.domain.dto.LoginDTO;
import com.le.leauth.domain.dto.RegisterDTO;
import com.le.leauth.domain.vo.LoginUserInformationVO;
import com.le.leauth.service.AuthService;
import com.le.lecommon.common.Constants;
import com.le.lecommon.common.RedisConstants;
import com.le.lecommon.entity.SysRole;
import com.le.lecommon.entity.SysRoleUser;
import com.le.lecommon.entity.SysUser;
import com.le.lecommon.exception.ServiceException;
import com.le.lecommon.mapper.SysRoleUserMapper;
import com.le.lecommon.mapper.SysUserMapper;

import com.le.lecommon.mapper.SysRoleMapper;
import com.le.lecommon.utils.BeanCopyUtil;
import com.le.lecommon.utils.EmailUtil;
import com.le.lecommon.utils.IpUtil;
import com.le.lecommon.utils.RedisUtil;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthGiteeRequest;
import me.zhyd.oauth.request.AuthGithubRequest;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import net.bytebuddy.implementation.bytecode.Throw;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.function.support.RouterFunctionMapping;

import javax.mail.MessagingException;
import javax.security.auth.message.AuthException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.UUID;

import static cn.dev33.satoken.util.SaFoxUtil.getRandomString;

@Slf4j
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {

    private final SysUserMapper userMapper;
    private final SysRoleMapper sysroleMapper;
    private final SysRoleUserMapper sysRoleUserMapper;

    private final GiteeConfigPropertis giteeConfigPropertis;
    private final GithubConfigPropertis githubConfigPropertis;

    private final EmailUtil emailUtil;
    private final RedisUtil redisUtil;
    private final RouterFunctionMapping routerFunctionMapping;

    //头像
    private final String[] avatarList = {
            "https://api.dicebear.com/6.x/pixel-art/svg?seed=Raccoon",
            "https://api.dicebear.com/6.x/pixel-art/svg?seed=Kitty",
            "https://api.dicebear.com/6.x/pixel-art/svg?seed=Puppy",
            "https://api.dicebear.com/6.x/pixel-art/svg?seed=Bunny",
            "https://api.dicebear.com/6.x/pixel-art/svg?seed=Fox"
    };

    /**
     * 用户登录并获取用户信息
     *
     * @param loginDTO
     * @return
     */
    @Override
    public LoginUserInformationVO loginAndGetInfo(LoginDTO loginDTO) {
        // 判断用户是否存在(如果不存在则提示) -> 判断密码是否正确(对于获取到的password进行加密
        // 和数据库的数据进行比对) -> 获取该用户的user表中的所以信息

        //1、获取用户信息
        SysUser sysUser = userMapper.selectByUsername(loginDTO.getUsername());
        //2. 验证是否可以登录
        log.info("验证用户{}",sysUser.toString());
        ifLogin(loginDTO, sysUser);

        //3. 进行登录以及获取token
        StpUtil.login(sysUser.getId());
        String tokenValue = StpUtil.getTokenValue();
        log.info("获取token:{}",tokenValue);

        LoginUserInformationVO loginUserInformationVO = BeanCopyUtil.copyObj(sysUser, LoginUserInformationVO.class);
        loginUserInformationVO.setToken(tokenValue);

        // 仅引入依赖 → 无需配置，直接使用 Session。 自带的一个session存入当前用户的所以信息
        StpUtil.getSession().set(Constants.CURRENT_USER, loginUserInformationVO);

        return loginUserInformationVO;
    }

    /**
     * 获取第三方授权地址
     * @param source
     * @return
     * @throws AuthException
     */
    @Override
    public String getAddress(String source) throws AuthException {
        AuthRequest authRequest =  GetA(source);
        // AuthStateUtils.createState() 是 OAuth2.0 安全机制的关键实现，通过生成随机 state 参数，
        // 确保授权流程的合法性和用户会话的安全性。其设计需满足不可预测性、唯一性和短暂性要求，以防止 CSRF 和重放攻击
        return authRequest.authorize(AuthStateUtils.createState()); // 生成授权 URL，并附加随机生成的 state 参数（用于防 CSRF 攻击）。
    }

    /**
     * 用户第三方登录
     * 获取第三方用户授权的信息
     * @param callback
     * @param httpServletResponse
     * @param source
     */
    @Override
    public void authLogin(AuthCallback callback, HttpServletResponse httpServletResponse, String source) throws AuthException, IOException {
        // 获取对应的第三方认证请求对象
        AuthRequest authRequest = GetA(source);
        // 调用第三方 SDK 的登录方法，验证授权码并  获取用户信息
        AuthResponse<AuthUser> response = authRequest.login(callback);
        // 如果用户取消登录
        if (response.getData() == null){
            log.info("用户取消了{}登录",source);
            // 重定向到首页 {这里是如何实现的？}
            httpServletResponse.sendRedirect("http://localhost:8080");
            return;
        }
        String re = (String) JSONObject.toJSON(response.getData());
        log.info("第三方登录的结果:{}",re);

        JSONObject jsonObject = JSONObject.parseObject(re);
        Object uuid = jsonObject.get("uuid");// uuid字段是默认的吗？

        //湖获取用户ip信息
        String ipAddress = IpUtil.getIp();
        String ip2region = IpUtil.getIp2region(ipAddress);
        //判断是否已经注册    getUsername, uuid所以说uuid是什么东西？
        SysUser sysUser = userMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, uuid));

        if (ObjectUtils.isEmpty(sysUser)){
            //保存账户信息
            SysUser user = SysUser.builder()
                    .username(uuid.toString())
                    .password(UUID.randomUUID().toString())
                    .loginType(source)
                    .lastLoginTime(LocalDateTime.now())
                    .ipLocation(ipAddress)
                    .ip(ip2region)
                    .status(Constants.YES)
                    .nickname(source + "-" + getRandomString(5)) // 获取5位随机长度的字符
                    .avatar(jsonObject.get("avatar").toString())
                    .build();

            userMapper.insert(user);
            //添加角色 [权限]
            addSysrole(user);

        }

    }

    /**
     * 发送邮件
     * @param email
     * @return
     */
    @Override
    public Boolean sendEmail(String email) throws MessagingException {
        emailUtil.sendCode(email);
        return true;
    }

    /**
     * 注册
     * @param registerDTO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)// 事务
    public Boolean register(RegisterDTO registerDTO) {
        // 邮箱不可重复、比对验证码（如果过期提示过期）、写入表中（设置其他参数）

        // 1、验证码是否过期
        ifCodeExpired(registerDTO);

        // 2、用户是否已经存在、邮箱是否已经注册
        SysUser sysUser = userMapper.selectOne(
                new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, registerDTO.getEmail())
                        .eq(SysUser::getUsername, registerDTO.getUsername())
        );
        if (sysUser != null){
            throw new ServiceException("当前用户已存在、邮箱已注册，请直接登录");
        }
        SysUser sysUserName = userMapper.selectOne(
                new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, registerDTO.getUsername())
        );
        SysUser sysUserEmail = userMapper.selectOne(
                new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, registerDTO.getEmail())
        );
        if (sysUserName != null && sysUserEmail == null){
            throw new ServiceException("该用户名已经存在、请换一个用户名");
        }
        if (sysUserEmail != null && sysUserName == null){
            throw new ServiceException("该邮箱已被其他用户注册，请更换邮箱");
        }

        //3、获取随机头像 并且写表
        String avatar = avatarList[(int) Math.random() * avatarList.length];
        SysUser user = SysUser.builder()
                .username(registerDTO.getUsername())
                .password(BCrypt.hashpw(registerDTO.getPassword()))
                .nickname(registerDTO.getUsername())
                .email(registerDTO.getEmail())
                .avatar(avatar)
                .status(Constants.YES)
                .build();
        userMapper.insert(user);
        //4、添加用户角色信息
        insertRole(user);
        //5、删除redis中的数据！！
        redisUtil.delete(RedisConstants.CAPTCHA_CODE_KEY + registerDTO.getEmail());
        return true;
    }

    /**
     * 修改密码
     * @param registerDTO
     * @return
     */
    @Override
    public Boolean forgotPassword(RegisterDTO registerDTO) {
        // 1、验证码检测是否过期
        ifCodeExpired(registerDTO);

        //2、检验是否存在该用户和邮箱
        SysUser sysUser = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
                .eq(SysUser::getUsername, registerDTO.getUsername())
                .eq(SysUser::getEmail, registerDTO.getEmail())
        );
        if (sysUser == null){
            throw new ServiceException("请检查用户名和邮箱是否正确,或者是否注册");
        }
        sysUser.setPassword(BCrypt.hashpw(registerDTO.getPassword()));
        userMapper.updateById(sysUser);
        redisUtil.delete(RedisConstants.CAPTCHA_CODE_KEY + registerDTO.getEmail());
        return true;
    }

    // 添加用户 [角色]
    private void insertRole(SysUser user) {
        SysRole role = sysroleMapper.selectOne(
                new LambdaQueryWrapper<SysRole>().eq(SysRole::getCode, Constants.USER)
        );
        sysroleMapper.addData(user.getId(),role.getId());
    }

    // 验证码是否过期
    private void ifCodeExpired(RegisterDTO registerDTO) {
        Object code = redisUtil.get(RedisConstants.CAPTCHA_CODE_KEY + registerDTO.getEmail());

        if(code == null || !code.toString().equals(registerDTO.getCode())){
            throw new ServiceException("验证码过期或输入错误");
        }
    }

    // 添加角色 [权限]
    private void addSysrole(SysUser user) {
        // 获取角色信息对应的id
        SysRole role = sysroleMapper.selectOne(new LambdaQueryWrapper<SysRole>().eq(SysRole::getCode, Constants.USER));

        SysRoleUser roleUser = new SysRoleUser()
                .builder()
                .userId(user.getId())
                .roleId(role.getId())
                .build();
        //加入对应的信息
        sysRoleUserMapper.insert(roleUser);

    }


    /**
     * 获取第三方授权方法
     * @param source
     * @return 不允许返回空
     */
    private @NonNull AuthRequest GetA(String source) throws AuthException {
        AuthRequest re = null;
        switch (source) {
            case "gitee" :
                re = new AuthGiteeRequest(AuthConfig.builder()
                        .clientId(giteeConfigPropertis.getAppId())
                        .clientSecret(giteeConfigPropertis.getAppSecret())
                        .redirectUri(giteeConfigPropertis.getRedirectUrl())
                        .build());
                break;
            case "github" :
                re = new AuthGithubRequest(AuthConfig.builder()
                        .clientId(githubConfigPropertis.getAppId())
                        .clientSecret(githubConfigPropertis.getAppSecret())
                        .redirectUri(githubConfigPropertis.getRedirectUrl())
                        .build());
                break;
            default:
                break;
        }
        if (re == null){
            throw new AuthException("授权地址无效");
        }
        return re;
    }

    /**
     * 判断用户方法
     * @param loginDTO
     * @param sysUser
     */
    private static void ifLogin(LoginDTO loginDTO, SysUser sysUser) {
        //1、进行用户比对
        if (sysUser == null) {
            throw new ServiceException("用户不存在");
        }
        //2、验证密码(哈希加密的方式)
        if (!BCrypt.checkpw(loginDTO.getPassword(), sysUser.getPassword())) {
            throw new ServiceException("用户名或密码错误");
        }
        //3. 验证状态
        if (sysUser.getStatus() == 0) {
            throw new ServiceException("用户被禁止使用");
        }
    }
}
